﻿Imports System.Reflection

Public Class ConverterToDataTable
    ''' <summary>
    '''             An array of the names of the System data types.
    ''' </summary>
    Private Shared ReadOnly _systemTypes As [String]() = New [String]() {"SYSTEM.BYTE", "SYSTEM.CHAR", "SYSTEM.DECIMAL", "SYSTEM.DOUBLE", "SYSTEM.INT16", "SYSTEM.INT32", _
     "SYSTEM.INT64", "SYSTEM.SBYTE", "SYSTEM.SINGLE", "SYSTEM.UINT16", "SYSTEM.UINT32", "SYSTEM.UINT64", _
     "SYSTEM.DATETIME", "SYSTEM.STRING", "SYSTEM.BOOLEAN"}

    Public Sub New()
    End Sub

    ''' <summary>
    '''             Converts an object into a data table.
    ''' </summary>
    ''' <param name="source">Spcifies the object to convert into a data table.</param>
    ''' <returns>Returns a data table created from an object.</returns>
    Public Shared Function ConvertToDataTable(ByVal source As [Object]) As DataTable
        Dim properties As PropertyInfo() = source.GetProperties()
        Dim dt As DataTable = CreateDataTable(properties, source)
        dt.TableName = source.Name
        FillData(properties, dt, Nothing, [String].Empty, source)
        Return dt
    End Function


    ''' <summary>  
    '''             Converts an array of objects into a data table.
    ''' </summary>
    ''' <param name="array">Specifies the array of objects that wil be used to create the data table.</param>
    ''' <returns>Returns a data table created from the array of objects.</returns>
    Public Shared Function ConvertToDataTable(ByVal array As [Object]()) As DataTable
        Dim properties As PropertyInfo() = array.[GetType]().GetElementType().GetProperties()
        Dim dt As DataTable = CreateDataTable(properties, Nothing)
        dt.TableName = array.[GetType]().GetElementType().Name

        If array.Length <> 0 Then
            For Each source As Object In array
                FillData(properties, dt, Nothing, [String].Empty, source)

            Next
        End If

        Return dt
    End Function


    ''' <summary>
    '''             Creates a new data table from the properties of an object.
    ''' </summary>
    ''' <param name="properties">Specifies the property information used to create the data table.</param>
    ''' <returns>Returns a data table created from an object.</returns>
    Private Shared Function CreateDataTable(ByVal properties As PropertyInfo(), ByVal source As Object) As DataTable
        Dim dt As New DataTable()
        CreateColumns(properties, dt, [String].Empty, source)
        Return dt
    End Function


    ''' <summary>
    '''             Creates the columns in a data table from the properties of an object.
    ''' </summary>
    ''' <param name="properties">Specifies the property information used to create the columns.</param>
    ''' <param name="dt">Specifies the data table being created.</param>
    ''' <param name="expandedName">Specifies the property name plus the nested object's property name when the column name is from a nested object.</param>
    Private Shared Sub CreateColumns(ByVal properties As PropertyInfo(), ByVal dt As DataTable, ByVal expandedName As [String], ByVal source As Object)
        Dim dc As DataColumn = Nothing
        Dim nestedProperties As PropertyInfo() = Nothing
        Dim nested As [Object] = Nothing

        For Each pi As PropertyInfo In properties
            If IsSystemType(pi.PropertyType.ToString()) OrElse pi.PropertyType.IsEnum Then
                dc = New DataColumn()
                dc.ColumnName = expandedName + pi.Name

                If pi.PropertyType.IsEnum Then
                    ' Enums always get the string type because we stuff the enum item name as the value.
                    dc.DataType = Type.[GetType]("System.String")
                Else
                    dc.DataType = pi.PropertyType
                End If

                dt.Columns.Add(dc)
            Else
                nested = pi.GetValue(source, Nothing)

                If pi.[GetType]().IsArray Then
                    nestedProperties = nested.[GetType]().GetElementType().GetProperties()
                Else
                    nestedProperties = nested.[GetType]().GetProperties()
                End If

                CreateColumns(nestedProperties, dt, pi.Name, nested)
            End If
        Next
    End Sub

    ''' <summary>
    '''             Fills a data row with values from an object.
    ''' </summary>
    ''' <param name="properties">Specifies the property that we are fill the data from.</param>
    ''' <param name="dt">Specifies the data table being fllled.</param>
    ''' <param name="row">Specifies the data row that will be filled with the property value.</param>
    ''' <param name="expandedName">Specifies the property name plus the nested object's property name when the column name is from a nested object.</param>
    ''' <param name="source">Specifies the object that contains the property being read..</param>
    Private Shared Sub FillData(ByVal properties As PropertyInfo(), ByVal dt As DataTable, ByVal row As DataRow, ByVal expandedName As [String], ByVal source As Object)
        Dim newRow As DataRow = Nothing
        Dim nestedProperties As PropertyInfo() = Nothing
        Dim nested As [Object] = Nothing

        If row Is Nothing Then
            newRow = dt.NewRow()
        Else
            newRow = row
        End If

        For Each pi As PropertyInfo In properties
            If IsSystemType(pi.PropertyType.ToString()) Then
                newRow(expandedName + pi.Name) = pi.GetValue(source, Nothing)
            ElseIf pi.PropertyType.IsEnum Then
                Dim i As Integer = 0
                Dim itemVal As Object = pi.GetValue(source, Nothing)
                Dim names As String() = [Enum].GetNames(itemVal.[GetType]())

                ' Pull the enum text into the column value.
                For Each o As Object In [Enum].GetValues(itemVal.[GetType]())
                    If o.ToString() = itemVal.ToString() Then
                        newRow(expandedName + pi.Name) = names(i)
                    End If
                    i += 1


                Next
            Else
                nested = pi.GetValue(source, Nothing)

                If pi.[GetType]().IsArray Then
                    nestedProperties = nested.[GetType]().GetElementType().GetProperties()
                Else
                    nestedProperties = nested.[GetType]().GetProperties()
                End If

                FillData(nestedProperties, dt, newRow, pi.Name, nested)

            End If
        Next

        If row Is Nothing Then
            dt.Rows.Add(newRow)
        End If
    End Sub


    ''' <summary>
    '''             IsSystemType determines if the name of a proerty type is one of the system data types.
    ''' </summary>
    ''' <param name="type">Specifies the name of the property type.</param>
    ''' <returns>True if the type is a system type, otherwise false.</returns>
    Private Shared Function IsSystemType(ByVal type As [String]) As Boolean
        Dim utype As [String] = type.ToUpper()

        For Each st As [String] In _systemTypes
            If utype = st Then
                Return True
            End If
        Next

        Return False
    End Function



End Class
